{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:15:08.625900Z",
     "start_time": "2025-01-21T15:14:57.217516Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:30:43.245299Z",
     "iopub.status.busy": "2025-01-21T15:30:43.244965Z",
     "iopub.status.idle": "2025-01-21T15:30:45.339075Z",
     "shell.execute_reply": "2025-01-21T15:30:45.338501Z",
     "shell.execute_reply.started": "2025-01-21T15:30:43.245277Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "https://www.kaggle.com/competitions/cifar-10/data\n",
    "\n",
    "```shell\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:15:09.104472Z",
     "start_time": "2025-01-21T15:15:08.852005Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:17.898066Z",
     "iopub.status.busy": "2025-01-21T15:31:17.897623Z",
     "iopub.status.idle": "2025-01-21T15:31:20.104540Z",
     "shell.execute_reply": "2025-01-21T15:31:20.104005Z",
     "shell.execute_reply.started": "2025-01-21T15:31:17.898041Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder):\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标签\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:28.092447Z",
     "iopub.status.busy": "2025-01-21T15:31:28.092106Z",
     "iopub.status.idle": "2025-01-21T15:31:28.149137Z",
     "shell.execute_reply": "2025-01-21T15:31:28.148614Z",
     "shell.execute_reply.started": "2025-01-21T15:31:28.092423Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:31.098313Z",
     "iopub.status.busy": "2025-01-21T15:31:31.097967Z",
     "iopub.status.idle": "2025-01-21T15:31:31.891245Z",
     "shell.execute_reply": "2025-01-21T15:31:31.890701Z",
     "shell.execute_reply.started": "2025-01-21T15:31:31.098289Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)}\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)}\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0]\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40),\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:38.471359Z",
     "iopub.status.busy": "2025-01-21T15:31:38.470911Z",
     "iopub.status.idle": "2025-01-21T15:31:38.474932Z",
     "shell.execute_reply": "2025-01-21T15:31:38.474350Z",
     "shell.execute_reply.started": "2025-01-21T15:31:38.471334Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "batch_size = 128\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T07:49:18.957606Z",
     "iopub.status.busy": "2025-01-21T07:49:18.957295Z",
     "iopub.status.idle": "2025-01-21T07:49:18.959964Z",
     "shell.execute_reply": "2025-01-21T07:49:18.959495Z",
     "shell.execute_reply.started": "2025-01-21T07:49:18.957586Z"
    }
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "# def cal_mean_std(ds):\n",
    "#     mean = 0.\n",
    "#     std = 0.\n",
    "#     for img, _ in ds:\n",
    "#         mean += img.mean(dim=(1, 2))\n",
    "#         std += img.std(dim=(1, 2))\n",
    "#     mean /= len(ds)\n",
    "#     std /= len(ds)\n",
    "#     return mean, std\n",
    "#\n",
    "# # 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:45.662563Z",
     "iopub.status.busy": "2025-01-21T15:31:45.662205Z",
     "iopub.status.idle": "2025-01-21T15:31:45.669403Z",
     "shell.execute_reply": "2025-01-21T15:31:45.668790Z",
     "shell.execute_reply.started": "2025-01-21T15:31:45.662539Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class Resdiual(nn.Module):\n",
    "    \"\"\"浅层的残差块，无bottleneck(瓶颈层)（性能限制）\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, use_1x1conv=False, stride=1):\n",
    "        \"\"\"\n",
    "        残差块\n",
    "        params filters: 过滤器数目，决定输出通道\n",
    "        params use_1x1conv: 是否使用 1x1 卷积，此时 stride=2，进行降采样\n",
    "        params strides: 步长，默认为1，当降采样的时候设置为2\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.conv1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=stride,\n",
    "            padding=1,\n",
    "        )\n",
    "        # 第二层卷积开始不改变通道数\n",
    "        self.conv2 = nn.Conv2d(\n",
    "            in_channels=output_channels,\n",
    "            out_channels=output_channels,\n",
    "            kernel_size=3,\n",
    "            stride=1,\n",
    "            padding=1,\n",
    "        )  \n",
    "        if use_1x1conv:\n",
    "            # skip connection 的 1x1 卷积，用于改变通道数和降采样，使得最终可以做残差连接\n",
    "            #因为当第一层的stride=2时，则输出的图像尺寸就会缩半，所以需要1x1卷积来改变尺寸,让input的尺寸和flow的尺寸相同，同样缩半\n",
    "            self.conv_sc = nn.Conv2d(\n",
    "                in_channels=input_channels,\n",
    "                out_channels=output_channels,\n",
    "                kernel_size=1,\n",
    "                stride=stride,\n",
    "            )\n",
    "        else:\n",
    "            self.conv_sc = None\n",
    "        \n",
    "        self.bn1 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        self.bn2 = nn.BatchNorm2d(output_channels, eps=1e-5, momentum=0.9)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        \"\"\"forward\"\"\"\n",
    "        flow = F.relu(self.bn1(self.conv1(inputs))) #卷积->BN->ReLU\n",
    "        flow = self.bn2(self.conv2(flow)) #卷积->BN\n",
    "        # print(f'flow shape: {flow.shape}')\n",
    "        if self.conv_sc:#如果有1x1卷积，就用1x1卷积\n",
    "            inputs = self.conv_sc(inputs)\n",
    "        # print(f'inputs shape: {inputs.shape}')\n",
    "        return F.relu(flow + inputs) #残差连接->ReLU，必须保证flow和inputs的shape相同\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:52.970663Z",
     "iopub.status.busy": "2025-01-21T15:31:52.970079Z",
     "iopub.status.idle": "2025-01-21T15:31:52.975484Z",
     "shell.execute_reply": "2025-01-21T15:31:52.974884Z",
     "shell.execute_reply.started": "2025-01-21T15:31:52.970639Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResdiualBlock(nn.Module):\n",
    "    \"\"\"若干个 Resdiual 模块堆叠在一起，通常在第一个模块给 skip connection 使用 1x1conv with stride=2\"\"\"\n",
    "    def __init__(self, input_channels, output_channels, num, is_first=False):\n",
    "        \"\"\"\n",
    "        params filters: 过滤器数目\n",
    "        params num: 堆叠几个 Resdiual 模块\n",
    "        params is_first: 是不是第一个block。 最上面一层 Resdiual 的 stride=1,is_first=False,图像尺寸减半，False图像尺寸不变\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential() # 用于存放 Resdiual 模块\n",
    "        self.model.append(Resdiual(\n",
    "            input_channels=input_channels, \n",
    "            output_channels=output_channels, \n",
    "            #is_first=True, 则stride=1,use_1x1conv=False,不用1*1卷积；反之，则用\n",
    "            use_1x1conv=not is_first, \n",
    "            stride=1 if is_first else 2   \n",
    "            )) # 第一个 Resdiual 模块，负责通道翻倍,图像的尺寸减半\n",
    "        \n",
    "        # 重复 num-1 次 Resdiual 模块\n",
    "        for _ in range(1, num):\n",
    "            # 堆叠 num 个 Resdiual 模块\n",
    "            self.model.append(Resdiual(\n",
    "                input_channels=output_channels, \n",
    "                output_channels=output_channels,\n",
    "                use_1x1conv=False, stride=1\n",
    "                ))\n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:20.834700Z",
     "start_time": "2025-01-21T07:44:20.831200Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:31:58.490278Z",
     "iopub.status.busy": "2025-01-21T15:31:58.489943Z",
     "iopub.status.idle": "2025-01-21T15:31:58.496623Z",
     "shell.execute_reply": "2025-01-21T15:31:58.496010Z",
     "shell.execute_reply.started": "2025-01-21T15:31:58.490256Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResNetForCifar10(nn.Module):\n",
    "    def __init__(self, n=3, num_classes=10):\n",
    "        \"\"\"\n",
    "        params units: 预测类别的数目\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(\n",
    "                in_channels=3,\n",
    "                out_channels=16,\n",
    "                kernel_size=3,\n",
    "                stride=1,\n",
    "            ),  # conv1  模拟resnet的第一层，将3通道变为16通道\n",
    "            nn.BatchNorm2d(16, momentum=0.9, eps=1e-5),\n",
    "            nn.ReLU(),\n",
    "            ResdiualBlock(input_channels=16, output_channels=16, num=2*n, is_first=True),  # conv2_x\n",
    "            ResdiualBlock(input_channels=16, output_channels=32, num=2*n),  # conv3_x\n",
    "            ResdiualBlock(input_channels=32, output_channels=64, num=2*n),  # conv4_x\n",
    "            # average pool\n",
    "            nn.AdaptiveAvgPool2d((1, 1)), #无论输入图片大小，输出都是1x1，把width和height压缩为1\n",
    "            nn.Flatten(),\n",
    "            # fully connected\n",
    "            nn.Linear(in_features=64, out_features=num_classes),\n",
    "            )\n",
    "        \n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 kaiming 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.kaiming_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, inputs):\n",
    "        return self.model(inputs)\n",
    "\n",
    "\n",
    "# for key, value in ResNetForCifar10(num_classes=len(class_names)).named_parameters():\n",
    "#     print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:42:15.755683Z",
     "start_time": "2025-01-21T07:42:15.746118Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:01.878281Z",
     "iopub.status.busy": "2025-01-21T15:32:01.877708Z",
     "iopub.status.idle": "2025-01-21T15:32:01.899004Z",
     "shell.execute_reply": "2025-01-21T15:32:01.898517Z",
     "shell.execute_reply.started": "2025-01-21T15:32:01.878258Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 565386\n"
     ]
    }
   ],
   "source": [
    "#模型总参数量\n",
    "total_params = sum(p.numel() for p in ResNetForCifar10(num_classes=len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T07:44:30.716019Z",
     "start_time": "2025-01-21T07:44:30.689319Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:03.925097Z",
     "iopub.status.busy": "2025-01-21T15:32:03.924758Z",
     "iopub.status.idle": "2025-01-21T15:32:03.952862Z",
     "shell.execute_reply": "2025-01-21T15:32:03.952291Z",
     "shell.execute_reply.started": "2025-01-21T15:32:03.925073Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 10])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#随机一个张量，喂给上面的model\n",
    "x = torch.randn(1, 3, 32, 32)\n",
    "model = ResNetForCifar10(num_classes=len(class_names))\n",
    "model(x).shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:05.467399Z",
     "iopub.status.busy": "2025-01-21T15:32:05.467047Z",
     "iopub.status.idle": "2025-01-21T15:32:05.495005Z",
     "shell.execute_reply": "2025-01-21T15:32:05.494498Z",
     "shell.execute_reply.started": "2025-01-21T15:32:05.467374Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:07.671865Z",
     "iopub.status.busy": "2025-01-21T15:32:07.671356Z",
     "iopub.status.idle": "2025-01-21T15:32:07.731868Z",
     "shell.execute_reply": "2025-01-21T15:32:07.731369Z",
     "shell.execute_reply.started": "2025-01-21T15:32:07.671836Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:09.985856Z",
     "iopub.status.busy": "2025-01-21T15:32:09.985384Z",
     "iopub.status.idle": "2025-01-21T15:32:09.991131Z",
     "shell.execute_reply": "2025-01-21T15:32:09.990613Z",
     "shell.execute_reply.started": "2025-01-21T15:32:09.985828Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:12.190711Z",
     "iopub.status.busy": "2025-01-21T15:32:12.190356Z",
     "iopub.status.idle": "2025-01-21T15:32:12.195101Z",
     "shell.execute_reply": "2025-01-21T15:32:12.194615Z",
     "shell.execute_reply.started": "2025-01-21T15:32:12.190687Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:32:14.297399Z",
     "iopub.status.busy": "2025-01-21T15:32:14.296996Z",
     "iopub.status.idle": "2025-01-21T15:35:11.705804Z",
     "shell.execute_reply": "2025-01-21T15:35:11.705241Z",
     "shell.execute_reply.started": "2025-01-21T15:32:14.297376Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 7040/7040 [02:56<00:00, 39.85it/s, epoch=19]\n"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())    \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = ResNetForCifar10(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 Rmsprop\n",
    "# Optimizers specified in the torch.optim package\n",
    "# >>> # Assuming optimizer uses lr = 0.05 for all groups\n",
    "# >>> # lr = 0.05     if epoch < 30\n",
    "# >>> # lr = 0.005    if 30 <= epoch < 80\n",
    "# >>> # lr = 0.0005   if epoch >= 80\n",
    "# >>> scheduler = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)\n",
    "\n",
    "class OptimizerWithScheduler:\n",
    "    def __init__(self, parameters, lr, momentum, weight_decay):\n",
    "        self.optimizer = torch.optim.SGD(parameters, lr=lr, momentum=momentum, weight_decay=weight_decay) # 优化器\n",
    "        self.scheduler = torch.optim.lr_scheduler.MultiStepLR(self.optimizer, milestones=[32_000, 48_000], gamma=0.1) # 学习率衰减\n",
    "        \n",
    "    def step(self):\n",
    "        self.optimizer.step()\n",
    "        self.scheduler.step()\n",
    "        \n",
    "    @property\n",
    "    def param_groups(self):\n",
    "        return self.optimizer.param_groups\n",
    "        \n",
    "    def zero_grad(self):\n",
    "        self.optimizer.zero_grad()\n",
    "        \n",
    "optimizer = OptimizerWithScheduler(model.parameters(), lr=0.1, momentum=0.9, weight_decay=1e-4)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "    \n",
    "exp_name = \"resnet34\"\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/{exp_name}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/{exp_name}\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_dl, \n",
    "    eval_dl, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=tensorboard_callback,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:36:07.416841Z",
     "iopub.status.busy": "2025-01-21T15:36:07.416454Z",
     "iopub.status.idle": "2025-01-21T15:36:07.667631Z",
     "shell.execute_reply": "2025-01-21T15:36:07.666981Z",
     "shell.execute_reply.started": "2025-01-21T15:36:07.416812Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAzYAAAHACAYAAABwG/1sAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA2FhJREFUeJzs3Xd4VGX2wPHvnZpOJ7RQpYOAiIhYQJqg2HtH144tq6usImLDtoj+RFldu2vZXcu6ikpEsQCCgCjSe+8tfdq9vz8mM7kzc6dmUiY5n+fhIXPnljNDhtyT877nVTRN0xBCCCGEEEKIFGaq7QCEEEIIIYQQoqoksRFCCCGEEEKkPElshBBCCCGEEClPEhshhBBCCCFEypPERgghhBBCCJHyJLERQgghhBBCpDxJbIQQQgghhBApTxIbIYQQQgghRMqz1HYAwVRVZdeuXWRnZ6MoSm2HI4QQDYqmaRQVFdGmTRtMJvndl4/8bBJCiNoRz8+lOpfY7Nq1i7y8vNoOQwghGrTt27fTrl272g6jzpCfTUIIUbti+blU5xKb7OxswBt8Tk5O3Me7XC7mzJnD6NGjsVqtyQ6v2kjcNScVY4bUjDsVY4bUjDtZMRcWFpKXl+f/v1h4yc+m1Ik7FWOG1Iw7FWMGibsmJSPmeH4u1bnExlfiz8nJSfiHR0ZGBjk5OSnzjw4Sd01KxZghNeNOxZghNeNOdswy3CqQ/GxKnbhTMWZIzbhTMWaQuGtSMmOO5eeSDKAWQgghhBBCpDxJbIQQQgghhBApTxIbIYQQQgghRMqrc3NshBB1l8fjweVyxby/y+XCYrFQXl6Ox+OpxsiSKxXjjjVms9mMxWKROTTVQNM03G634fufit9TkJpxJxKzfC6EqB8ksRFCxKS4uJgdO3agaVrMx2iaRqtWrdi+fXtK3TCkYtzxxJyRkUHr1q2x2Ww1FF3953Q62b17N6WlpYbPp+L3FKRm3InGLJ8LIVKfJDZCiKg8Hg87duwgIyODFi1axHyzoKoqxcXFZGVlpdRij6kYdywxa5qG0+lk//79bN68ma5du6bM66vLVFVl8+bNmM1m2rRpg81mC/mMpOL3FKRm3PHGLJ8LIeoPSWyEEFG5XC40TaNFixakp6fHfJyqqjidTtLS0lLqRiEV44415vT0dKxWK1u3bvXvL6rG6XSiqip5eXlkZGQY7pOK31OQmnEnErN8LoSoH+L+X+qHH35g/PjxtGnTBkVR+PTTT0P2Wb16NWeffTaNGjUiMzOTQYMGsW3btmTEK4SoRakyFEVElio3qKlG3tfUJv9+QqS+uD/FJSUl9OvXj5kzZxo+v3HjRk4++WR69OjBvHnz+P3335k8ebL89kMIIYQQQghRbeIeijZ27FjGjh0b9vkHHniAcePG8fTTT/u3denSJbHohBBCCCGEECIGSZ1jo6oqX3zxBX/5y18YM2YMv/76K506dWLSpEmce+65hsc4HA4cDof/cWFhIeAd0x9PW1kf3zGJHFubJO6ak4oxQ+3G7Ztjo6oqqqrGfJyvg5rv2FRhFHfnzp258847ufPOO6t8/nnz5jFixAgOHjxI48aNq3w+iO+9VlUVTdNwuVyYzeaA51LtcyHqjo4dO3LXXXdx11131XYoQogGKqmJzb59+yguLubJJ5/kscce46mnnuKrr77i/PPP57vvvuO0004LOWbatGlMnTo1ZPucOXPCTsKMRUFBQcLH1iaJu+akYsxQO3FbLBZatWpFcXExTqcz7uOLioqqIarozjrrLPr27cu0adMSOl4f9zfffENGRob/ly9V4WsJXFRUlPRx/bG8106nk7KyMn744QfcbrdhbKJhGDZsGP3792fGjBlVPtcvv/xCZmZm1YMSQogEJb1iA3DOOedw9913A9C/f38WLFjArFmzDBObSZMmkZ+f739cWFhIXl4eo0ePJicnJ+4YXC4XBQUFjBo1CqvVmuArqXkSd81JxZihduMuLy9n+/btZGVlxTVfTtM0ioqKyM7OrpXGAxaLBZvNFvb/Ek3T8Hg8WCyWkO3BcSfy/1E4vl/aZGdnJ+288bzX5eXlpKenc+qpp4b8eyYjcRP1h2/R0Vi0aNGimqMRQojIkvqrwubNm2OxWOjVq1fA9p49e4btima328nJyQn4A2C1WhP6Y//lZYav/iv25W8mfI7a+lOV1y1x1/+YaztuRVEwmUyYTCYURaHcrUb9U+byUOb0UObyxLR/LH/0cUT6c9111/H999/zwgsvYDabMZvNvP3225jNZr7++msGDRpEeno6CxYsYPPmzZx33nm0bt2anJwcTjzxRObNmxdwrc6dO/PCCy/4H5vNZl5//XUuuOACsrKy6N69O59//nlMsfmqNPrHn3zyCX379iU9PZ3OnTvz3HPPBTw/a9Ysunfv7l9E8OKLL/Y/9/HHH9O/f39at25Ny5YtGT16NGVlZRGvryhKxO8zUTWaplHqdAf8KXN6QrYl+088C+hee+21fP/99zz//PMoioKiKLz55psoisKXX37JwIEDSU9P5+eff2bjxo2cc8455ObmkpWVxaBBg/jmm28CztexY8eAyo+iKPzjH//gvPPOIyMjg65du/LZZ5/FFJvH4+H666+nU6dOpKen0717d55//vmQ/V5//XV69+6N3W6ndevWTJw40f/ckSNHuOmmm8jNzSUtLY0+ffrw+eefx/z+CFHdth8q5ZrXF7Ngw4HaDqXeSGrFxmazMWjQINauXRuwfd26dXTo0CGZlwqvdD855TvwFO6omesJ0QCVuTz0eujrWrn2qkfGkGGL/l/X888/z7p16+jTpw+PPPIIACtXrgTg/vvv59lnn6Vz5840adKE7du3M27cOB5//HHsdjtvvfUWl112GatXr6Zjx45hrzF16lSefvppnnnmGf7v//6PK664gq1bt9K0adO4XtPSpUu5+OKLefjhh7nkkktYsGABt956K82aNePaa69lyZIl3HHHHbzzzjucdNJJHDp0iB9//BGA3bt3c9lll/HUU08xcuRINE1j/vz5cd3giuSrrc9IrJ8PiO0z0rFjRywWC0eOHAn4jLz99tuMHz+etWvX0r59+7DXSPQzoqoq7dq149///jfNmjVjwYIF3Hjjjf6kHuDll18mPz+fJ598krFjx3L06FHmz5/vP/7MM8+kqKiId999ly5durBq1aqQOWVC1Ka7P1zOkq2H+X7dfrY8eWZth1MvxJ3YFBcXs2HDBv/jzZs3s3z5cpo2bUr79u259957ueSSSzj11FMZPnw4X331Ff/73/+YN29eMuMOz5bl/dtZUjPXE0LUSY0aNcJms5GRkUGrVq0AWLNmDQCPPPIIo0aN8u/btGlT+vXr53/8yCOP8NFHH/G///2P22+/Pew1rr32Wi677DIAnnjiCV544QUWL17MGWecEVes06dPZ8SIEUyePBmAbt26sWrVKp555hmuvfZatm3bRmZmJmeddRbZ2dl06NCBAQMGAN7Exu12c95559GkSRNycnICXosQ4cTyGVFVlcLCwoDvOYBHH32UTz75hM8++yygShIs0c+I1WoNmH/bqVMnFi5cyL/+9S9/YvPYY4/x5z//OaChx6BBg1BVlXnz5rF48WJWr15Nt27dAG8DECHqkt1Hy2s7hHon7sRmyZIlDB8+3P/YNz/mmmuu4c033+S8885j1qxZTJs2jTvuuIPu3bvz0UcfcfLJJycv6kgqEhvFWVwz1xOiAUq3mln1yJio+6mqSlFhEdk52UmbJJ9urfpvXI8//viAx8XFxTz88MN88cUX/kShrKws6sLCxx57rP/rzMxMcnJy2LdvX9zxrF69mnPOOSdg29ChQ5kxYwYej4dRo0bRoUMHOnfuzBlnnMEZZ5zhH97Tr18/RowYQb9+/Tj99NMZO3YsF198MU2aNIk7DpE8wZ+R6vgshLtuMhh9Rh555JEa/YzMnDmT119/nW3btlFWVobT6aR///6At1nRrl27GDFihOGxK1asoF27dv6kRgjRMMSd2AwbNizqEIfrrruO6667LuGgqkLzVWwcktgIUV0URYlpuIuqqrhtZjJsljq1qndw56Z77rmHgoICnn32WY455hjsdjsXXHBB1A5wwfNRFEWplrbW2dnZLFu2jHnz5jFnzhweeughHn74YX755RcaN25MQUEBP/30E59//jkzZ85k8uTJLFq0iE6dOiU9FhGb4M9IXf0shBP8Gbn33nv55ptv/J+R9PR0Lrzwwmr7jHzwwQfcc889/O1vf2PIkCFkZ2fzzDPPsGjRIgDS09MjHh/teSFE/VT3/3eNl903FE0SGyEaOpvNhsfjibrf/PnzufbaaznvvPPo27cvrVq1ivqb6GTq2bOnf26APqZu3br55wRYLBZGjhzJ008/ze+//86WLVv49ttvAe/N4tChQ5k0aRJLly7FZrPxySef1Fj8InXF+hlZsGBByGdky5Yt1RbX/PnzOemkk7j11lsZMGAAxxxzDBs3bvQ/n52dTceOHZk7d67h8b1792bHjh2sW7eu2mIUQtQ9SW0eUCfYJLERQnh17NiRRYsWsWXLFrKyssL+prhr1658/PHHjB8/HkVRePDBB2t08v2f//xnBg0axKOPPsoll1zCwoULefHFF3nppZcA+Pzzz9m0aROnnnoqTZo0Yfbs2aiqSvfu3Vm0aBFz585l5MiRpKens2rVKvbv30/Pnj1rLH6RumL9jBxzzDEBn5HJkydX66K7Xbt25e233+brr7+mU6dOvPPOO/zyyy8BVciHH36Ym2++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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    \n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T15:36:10.148882Z",
     "iopub.status.busy": "2025-01-21T15:36:10.148401Z",
     "iopub.status.idle": "2025-01-21T15:36:10.880282Z",
     "shell.execute_reply": "2025-01-21T15:36:10.879706Z",
     "shell.execute_reply.started": "2025-01-21T15:36:10.148857Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.6478\n",
      "accuracy: 0.7836\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/{exp_name}/best.ckpt\", weights_only=True,map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T07:52:19.663752Z",
     "iopub.status.busy": "2025-01-21T07:52:19.663487Z",
     "iopub.status.idle": "2025-01-21T07:54:19.176801Z",
     "shell.execute_reply": "2025-01-21T07:54:19.176329Z",
     "shell.execute_reply.started": "2025-01-21T07:52:19.663728Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 2344/2344 [01:59<00:00, 19.62it/s]\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>deer</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath       class\n",
       "0  competitions/cifar-10/test/1.png        deer\n",
       "1  competitions/cifar-10/test/2.png    airplane\n",
       "2  competitions/cifar-10/test/3.png  automobile\n",
       "3  competitions/cifar-10/test/4.png        ship\n",
       "4  competitions/cifar-10/test/5.png    airplane"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = []\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data)\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]\n",
    "    preds_collect.extend(preds)\n",
    "    \n",
    "test_df[\"class\"] = preds_collect\n",
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T07:54:19.177784Z",
     "iopub.status.busy": "2025-01-21T07:54:19.177388Z",
     "iopub.status.idle": "2025-01-21T07:54:19.494510Z",
     "shell.execute_reply": "2025-01-21T07:54:19.493915Z",
     "shell.execute_reply.started": "2025-01-21T07:54:19.177764Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df.to_csv(\"submission.csv\", index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
